#ifdef HAVE_CONFIG_H
#include <config.h> /* for GETTEXT_PACKAGE */
#endif

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <unistd.h>
#include <ctype.h>

#include <glib.h>
#include <glib/gprintf.h>
#include <glib-object.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>

#include <libnautilus-extension/nautilus-extension-types.h>
#include <libnautilus-extension/nautilus-menu-provider.h>
#include <libnautilus-extension/nautilus-info-provider.h>

#include <stdlib.h>

#include "nautilus-nutstore.h"
#include "command-client.h"

/**
 * emblems name
 */
static const char *emblems[] = { "nutstore-uptodate", "nutstore-syncing", "nutstore-locked" };
/** 
 * commands name
 */
static const gchar * COMMAND_GET_NS_ROOTS = "get_ns_roots";

static const gchar * COMMAND_QUERY_STAT = "query_stat";
static const gchar * COMMAND_REFRESH_STAT = "refresh_stat";
static const gchar * COMMAND_REFRESH_NS_ROOT = "refresh_ns_root";

static const gchar * COMMAND_SHOW_PROPERTIES = "show_sync_folder_properties";
static const gchar * COMMAND_PUBLISH_OBJ = "publish_obj";
static const gchar * COMMAND_CREATE_SANDBOX = "create_sync_folder";
static const gchar * COMMAND_INVITE_FRIENDS = "invite_friends";

static const gchar * ARGUMENT_KEY_PATH = "path";

/**
 * menu string
 */
static const gchar *MENU_LABEL_NUTSTORE = "Nutstore";
static const gchar *MENU_LABEL_SHOW_PROPERTIES = "Properties";
static const gchar *MENU_LABEL_PUBLISH = "Quick URL Share";
static const gchar *MENU_LABEL_CREATE_SANDBOX = "Sync Only For Me";
static const gchar *MENU_LABEL_INVITE_FRIENDS = "Sync With Friends";

static const gchar *MENU_TIP_SHOW_PROPERTIES = "Show the properties of the Nutstore sync folder"; 
static const gchar *MENU_TIP_PUBLISH = "Share to somebody else by URL"; 
static const gchar *MENU_TIP_CREATE_SANDBOX = "Sync the folder only for myself"; 
static const gchar *MENU_TIP_INVIATE_FRIENDS = "Sync the folder with friends"; 

static const gchar *MENU_ICON_NUTSTORE = "nutstore-small";
static const gchar *MENU_ICON_SHOW_PROPERTIES = "nutstore-small";
static const gchar *MENU_ICON_PUBLISH = "nutstore-group";
static const gchar *MENU_ICON_CREATE_SANDBOX = "nutstore-small";    // FIXME
static const gchar *MENU_ICON_INVITE_FRIENDS = "nutstore-group";    // FIXME

static const gchar *MENU_DATA_KEY_FILE = "nutstore_data_files";
 
/**
 * global workaround flags
 */
gboolean enable_file_info_in_progress_workaround;
gboolean enable_submenu_workaround; 

/**
 * repsonse handlers
 */
static void file_info_command_response_handler(NutstoreCommand *cmd);
static void get_ns_root_command_response_handler(NutstoreCommand *cmd);

/**
 * reset functions 
 */
static void reset_ns_root_dirs(NautilusNutstore *nuts, gchar **paths);
static void reset_file(NautilusFileInfo *file);
static void reset_all_files(NautilusNutstore *nuts);

/**
 * resolve special path component: ".." and "."
 */
static gchar * canonicalize_path(gchar *path) 
{
    int i, j = 0;
    gchar *toret, **cpy, **elts;

    g_assert(path != NULL);
    g_assert(path[0] == '/');

    elts = g_strsplit(path, "/", 0);
    cpy = g_new(gchar *, g_strv_length(elts)+1);
    cpy[j++] = "/";
    for (i = 0; elts[i] != NULL; i++) {
        if (strcmp(elts[i], "..") == 0) {
            j--;
        } else if (strcmp(elts[i], ".") != 0 && elts[i][0] != '\0') {
            cpy[j++] = elts[i];
        }
    }

    cpy[j] = NULL;
    toret = g_build_filenamev(cpy);
    g_free(cpy);
    g_strfreev(elts);

    return toret;
}

/**
 * get UTF8 of file path
 * you should free the returned object after using
 */
static gchar * file_info_get_utf8(NautilusFileInfo *file) 
{
    gchar *uri;
    gchar *filename;
    gchar *utf8;

    uri = nautilus_file_info_get_uri(file);
    filename = uri ? g_filename_from_uri(uri, NULL, NULL) : NULL;
    g_free(uri);

    if (filename) {
        utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
    } else {
        utf8 = NULL;
    }

    if (utf8 == NULL) {
        g_printf("file wasn't correctly encoded %s\n", filename);
    }

    g_free(filename);

    return utf8;
}

static gboolean 
nutstore_is_child_of_path(const gchar *path, const char *other)
{   
    if (path == NULL || other == NULL) {
        return FALSE;
    }

    if (g_str_has_prefix(path, other)) {
        size_t path_len = strlen(path);
        size_t other_len = strlen(other);
 
        g_assert(path_len >= other_len);
        if (path_len == other_len || other[other_len-1] == '/' || 
                path[other_len] == '/') 
        {
            return TRUE;
        }
    }

    return FALSE;
}

/**
 * return the path of sandbox which contains the filename
 * return NULL if the file is not in a sandbox
 */
static const gchar * 
nutstore_find_parent_sandbox(NautilusNutstore *nuts, const gchar *filename)
{
    g_assert(nuts != NULL);
    g_assert(filename != NULL);

    const gchar *toret = NULL;
    int i;

    g_mutex_lock(nuts->ns_root_dirs_mutex);

    if (nuts->ns_root_dirs!= NULL) {
        gchar **dirs = nuts->ns_root_dirs;

        for (i = 0; dirs[i] != NULL; i++) {
            if (nutstore_is_child_of_path(filename, dirs[i])) {
                toret = dirs[i];
                break;
            }
        }
    }

    g_mutex_unlock(nuts->ns_root_dirs_mutex);

    return toret;
}

static gboolean 
nutstore_has_child_sandbox(NautilusNutstore *nuts, const gchar *filename)
{
    g_assert(nuts != NULL);
    g_assert(filename != NULL);

    gboolean has_child = FALSE;
    int i;

    g_mutex_lock(nuts->ns_root_dirs_mutex);

    if (nuts->ns_root_dirs!= NULL) {
        gchar **dirs = nuts->ns_root_dirs;

        for (i = 0; dirs[i] != NULL; i++) {
            if (nutstore_is_child_of_path(dirs[i], filename)) {
                has_child = TRUE;
                break;
            }
        }
    }

    g_mutex_unlock(nuts->ns_root_dirs_mutex);

    return has_child;
}

/**
 * return whether file or directory (UTF8 filename) is in nutstore sandbox.
 * return FALSE if command client isn't connected or we can't get root dirs.
 */
gboolean 
nutstore_is_file_in_sandbox(NautilusNutstore *nuts, const gchar *filename)
{
    const gchar *dir = nutstore_find_parent_sandbox(nuts, filename);
    return dir != NULL;
}


/** 
 * notify Nautilus to refresh file info
 */
static void reset_ns_root_dirs(NautilusNutstore *nuts, gchar **paths)
{
    g_assert(nuts != NULL);

    g_mutex_lock(nuts->ns_root_dirs_mutex);

    if (nuts->ns_root_dirs != NULL) {
        g_strfreev(nuts->ns_root_dirs);
        nuts->ns_root_dirs = NULL;
    }

    if (paths != NULL) {
        gchar **dirs = g_new0(gchar *, g_strv_length(paths)+1);
        g_assert(dirs != NULL);

        int i, n = 0;
        for (i = 0; paths[i] != NULL; i++) {
            g_assert(paths[i][0] == '/');
            dirs[n++] = canonicalize_path(paths[i]);
        }
        dirs[n] = NULL;

        nuts->ns_root_dirs = dirs;

        gchar *str = g_strjoinv(", ", dirs);
        debug("Update nutstore root dirs to %s\n", str);
        g_free(str);
    } else {
        debug("Update nutstore root dir to NULL\n");
    }

    g_mutex_unlock(nuts->ns_root_dirs_mutex);
}

static void reset_file(NautilusFileInfo *file) 
{
    //g_printf("resetting file %p\n", (void *) file);
    nautilus_file_info_invalidate_extension_info(file);
}

static void reset_all_files(NautilusNutstore *nuts) {
    /* Only run this on the main loop or you'll cause problems. */

    /* this works because you can call a function pointer with
       more arguments than it takes */
    g_hash_table_foreach(nuts->obj2filename, (GHFunc) reset_file, NULL);
}

/**
 * UDP input command handler
 */
static void input_command_refresh_state_handler(NutstoreInputCommand *cmd)
{
    gchar **path;

    path = g_hash_table_lookup(cmd->command_args, ARGUMENT_KEY_PATH);
    if (path == NULL || path[0] == NULL || path[0][0] != '/') {  
        debug("Unknown path to refresh");
        return;
    }

    NautilusNutstore *nuts = (NautilusNutstore *)cmd->user_data;
    NautilusFileInfo *file;

    gchar *parent_path = NULL;
    gchar *filename = canonicalize_path(path[0]);
    const gchar *sandbox = nutstore_find_parent_sandbox(nuts, filename);
    
    if (sandbox == NULL) {
        g_free(filename);
        return;
    }

    //refresh the emblems of the object and its parent directories
    while (strcmp(filename, "/") != 0 && g_str_has_prefix(filename, sandbox)) {
        debug("UDP refresh state for %s", filename);

        file = g_hash_table_lookup(nuts->filename2obj, filename);
        if (file != NULL) {
            debug("gonna reset %s", filename);
            reset_file(file);
        }

        //the filename should contain directory part
        parent_path = g_path_get_dirname(filename);
        g_assert(strcmp(parent_path, ".") != 0);

        g_free(filename);
        filename = parent_path;
    } 

    g_free(filename);
}

static void input_command_refresh_ns_root_handler(NutstoreInputCommand *cmd)
{
    g_assert(cmd != NULL);
    g_assert(cmd->user_data != NULL);

    NautilusNutstore *nuts = (NautilusNutstore *)cmd->user_data;

    // reset nutstore root dir and all files' status
    reset_ns_root_dirs(nuts, NULL);
    reset_all_files(nuts);
   
    // get nutstore root dir from server
    nutstore_command_client_send_command(&(nuts->client),
            get_ns_root_command_response_handler, TRUE, nuts, 
            COMMAND_GET_NS_ROOTS, NULL);
}

static void command_client_on_connect(NautilusNutstore *nuts) 
{
    // reset nutstore root dir and all files' status
    reset_ns_root_dirs(nuts, NULL);
    reset_all_files(nuts);
   
    // get nutstore root dir from server
    nutstore_command_client_send_command(&(nuts->client),
            get_ns_root_command_response_handler, TRUE, nuts, 
            COMMAND_GET_NS_ROOTS, NULL);
}

static void command_client_on_disconnect(NautilusNutstore *nuts) 
{
    // reset nutstore root dir and all files' status
    reset_ns_root_dirs(nuts, NULL);
    reset_all_files(nuts); 
}

/**
 * Weak reference callback
 */ 
static void when_file_dies(NautilusNutstore *nuts, NautilusFileInfo *file) 
{
    gchar *filename = NULL;

    filename = g_hash_table_lookup(nuts->obj2filename, file);

    /* we never got a chance to view this file */
    if (filename == NULL) {
        return;
    }

    g_hash_table_remove(nuts->filename2obj, filename);
    g_hash_table_remove(nuts->obj2filename, file);
}

static void changed_cb(NautilusFileInfo *file, NautilusNutstore *nuts) 
{
    /* check if this file's path has changed;
     * if so update the hash and invalidate the file 
     */
    gchar *filename, *pfilename;
    gchar *filename2;
    gchar *uri;

    uri = nautilus_file_info_get_uri(file);
    pfilename = g_filename_from_uri(uri, NULL, NULL);
    filename = pfilename ? canonicalize_path(pfilename) : NULL;
    filename2 =  g_hash_table_lookup(nuts->obj2filename, file);

    g_free(pfilename);
    g_free(uri);

    /* if filename2 is NULL we've never seen this file in update_file_info */
    if (filename2 == NULL) {
        g_free(filename);
        return;
    }

    if (filename == NULL) {
        /* A file has moved to offline storage. Lets remove it from our tables. */
        g_object_weak_unref(G_OBJECT(file), (GWeakNotify) when_file_dies, nuts);
        g_hash_table_remove(nuts->filename2obj, filename2);
        g_hash_table_remove(nuts->obj2filename, file);
        g_signal_handlers_disconnect_by_func(file, G_CALLBACK(changed_cb), nuts);
        reset_file(file);
        return;
    }

    /* this is a hack: because nautilus doesn't do this for us, 
     * for some reason the file's path has changed 
     */
    if (strcmp(filename, filename2) != 0) {
        debug("replace old: %s, new %s", filename2, filename);

        /* gotta do this first, the call after this frees filename2 */
        g_hash_table_remove(nuts->filename2obj, filename2);

        g_hash_table_replace(nuts->obj2filename, file, g_strdup(filename));

        {
            NautilusFileInfo *f2;

            /* we shouldn't have another mapping from filename to an object */
            f2 = g_hash_table_lookup(nuts->filename2obj, filename);
            if (f2 != NULL) {
                /* lets fix it if it's true, just remove the mapping */
                g_hash_table_remove(nuts->filename2obj, filename);
                g_hash_table_remove(nuts->obj2filename, f2);
            }
        }

        g_hash_table_insert(nuts->filename2obj, g_strdup(filename), file);
        reset_file(file);
    }

    g_free(filename);
}

/**
 * File info command response handler
 */
static void file_info_command_response_handler(NutstoreCommand *cmd)
{
    FileInfoUserData *user_data = (FileInfoUserData *)cmd->user_data;

    if (!cmd->cancelled) {
        gchar **stat = NULL;

        if (cmd->response && (stat = g_hash_table_lookup(cmd->response, "stat")) != NULL) {
            /* state code: 
             *     0, up to date
             *     1, syncing
             *     2, locked
             *     3, won't sync
             *     other, invalid.
             */
            int stat_code = strtol(stat[0], NULL, 0);

            if (stat_code >= 0 && stat_code < 3) {
                //g_printf("stat_code=%d\n", stat_code);
                nautilus_file_info_add_emblem(user_data->file, emblems[stat_code]);
            }
        }

        // Nofity Nautilus that file info update complete
        if (!enable_file_info_in_progress_workaround) {
            nautilus_info_provider_update_complete_invoke(user_data->update_complete,
                    user_data->provider,
                    (NautilusOperationHandle *)cmd,
                    NAUTILUS_OPERATION_COMPLETE);
        }
    }

    // We're done with file info handles; release user data resource
    g_closure_unref(user_data->update_complete);
    g_object_unref(user_data->file);
    g_free(user_data); 
}

static void get_ns_root_command_response_handler(NutstoreCommand *cmd) 
{
    gchar **paths = g_hash_table_lookup(cmd->response, ARGUMENT_KEY_PATH);
    if (paths != NULL) {  
        NautilusNutstore *nuts = (NautilusNutstore *)cmd->user_data;
        g_assert(nuts != NULL);

        // reset nutstore root dir and all files' status
        reset_ns_root_dirs(nuts, paths);
        reset_all_files(nuts);
    }
}

/**
 * Implementation of Nautilus file info interface
 */
static NautilusOperationResult
nautilus_nutstore_update_file_info(NautilusInfoProvider    *provider,
        NautilusFileInfo         *file,
        GClosure                 *update_complete,
        NautilusOperationHandle **handle) 
{
    NautilusNutstore *nuts;
    gchar *path;

    nuts = NAUTILUS_NUTSTORE(provider);

    /* this code adds this file object to our two-way hash of file objects
     *  so we can shell touch these files later 
     */
    {
        gchar *pfilename, *uri;

        uri = nautilus_file_info_get_uri(file);
        pfilename = g_filename_from_uri(uri, NULL, NULL);
        g_free(uri);

        if (pfilename == NULL) {
            return NAUTILUS_OPERATION_COMPLETE;
        } else {
            int cmp = 0;
            gchar *stored_filename;
            gchar *filename;

            filename = canonicalize_path(pfilename);
            g_free(pfilename);
            stored_filename = g_hash_table_lookup(nuts->obj2filename, file);

            if (stored_filename == NULL || (cmp = strcmp(stored_filename, filename)) != 0){
                if (stored_filename != NULL) {
                    /* this happens when the filename changes name on a file obj 
                       but changed_cb isn't called */
                    g_object_weak_unref(G_OBJECT(file), (GWeakNotify) when_file_dies, nuts);
                    g_hash_table_remove(nuts->obj2filename, file);
                    g_hash_table_remove(nuts->filename2obj, stored_filename);
                    g_signal_handlers_disconnect_by_func(file, G_CALLBACK(changed_cb), nuts);
                } else {
                    NautilusFileInfo *f2;

                    if ((f2 = g_hash_table_lookup(nuts->filename2obj, filename)) != NULL) {
                        /* if the filename exists in the filename2obj hash
                           but the file obj doesn't exist in the obj2filename hash:

                           this happens when nautilus allocates another file object
                           for a filename without first deleting the original file object

                           just remove the association to the older file object, it's obsolete
                         */
                        g_object_weak_unref(G_OBJECT(f2), (GWeakNotify) when_file_dies, nuts);
                        g_signal_handlers_disconnect_by_func(f2, G_CALLBACK(changed_cb), nuts);
                        g_hash_table_remove(nuts->filename2obj, filename);
                        g_hash_table_remove(nuts->obj2filename, f2);
                    }
                }

                /* too chatty */
                /* debug("adding %s <-> 0x%p", filename, file);*/
                g_object_weak_ref(G_OBJECT(file), (GWeakNotify) when_file_dies, nuts);
                g_hash_table_insert(nuts->filename2obj, g_strdup(filename), file);
                g_hash_table_insert(nuts->obj2filename, file, g_strdup(filename));

                // connect a callback to file object changed event
                g_signal_connect(file, "changed", G_CALLBACK(changed_cb), nuts);
            }

            g_free(filename);
        }
    }
   
    // Make sure the file info is still available 
    if (nautilus_file_info_is_gone(file)) {
        debug("file info is gone!");
        return NAUTILUS_OPERATION_COMPLETE;
    }

    // Get UTF8 file name
    path = file_info_get_utf8(file);
    if (path == NULL) {
        return NAUTILUS_OPERATION_COMPLETE;
    }

    // If the file isn't in nutstore dirctory, we can't do anything
    if (nutstore_is_file_in_sandbox(nuts, path) == FALSE) {
        debug("file \"%s\" isn't in nutstore root dir\n", path);
        g_free(path);
        return NAUTILUS_OPERATION_COMPLETE;
    }

    // If the command client isn't connected, we can't do anything
    if (nutstore_command_client_is_connected(&(nuts->client)) == FALSE) {
        g_free(path);
        return NAUTILUS_OPERATION_COMPLETE;
    }

    // Create and send to client
    {
        NutstoreCommand *cmd;
        FileInfoUserData *user_data = g_new0(FileInfoUserData, 1);

        user_data->provider = provider;
        user_data->update_complete = g_closure_ref(update_complete);
        user_data->file = g_object_ref(file);

        cmd = nutstore_command_new(file_info_command_response_handler, TRUE, user_data, 
                COMMAND_QUERY_STAT, ARGUMENT_KEY_PATH, path, NULL);

        nutstore_command_client_request(&(nuts->client), cmd);

        *handle = (NautilusOperationHandle *)cmd;
        g_free(path);

        return enable_file_info_in_progress_workaround ? NAUTILUS_OPERATION_COMPLETE : 
            NAUTILUS_OPERATION_IN_PROGRESS;
    }
}

static void
nautilus_nutstore_cancel_update(NautilusInfoProvider     *provider,
        NautilusOperationHandle  *handle) 
{
    NutstoreCommand *cmd = (NutstoreCommand *) handle;
    cmd->cancelled = TRUE;
    return;
}

static void
menu_item_cb_common_simple(NautilusMenuItem *item, gpointer user_data, 
        const gchar *cmd)
{
    GList *files;
    gchar *path;
    NautilusNutstore *nuts;

    files = g_object_get_data(G_OBJECT(item), MENU_DATA_KEY_FILE);

    path = file_info_get_utf8(NAUTILUS_FILE_INFO(files->data));
    if (path == NULL) {
        return;
    }

    nuts = NAUTILUS_NUTSTORE(user_data);

    nutstore_command_client_send_command(&(nuts->client), NULL, FALSE, NULL,
            cmd, ARGUMENT_KEY_PATH, path, NULL);

    g_free(path); 
}

static void 
menu_item_show_properties_cb(NautilusMenuItem *item, gpointer user_data)
{
    menu_item_cb_common_simple(item, user_data, COMMAND_SHOW_PROPERTIES);
}

static void 
menu_item_publish_cb(NautilusMenuItem *item, gpointer user_data)
{
    menu_item_cb_common_simple(item, user_data, COMMAND_PUBLISH_OBJ);
}

static void 
menu_item_create_sandbox_cb(NautilusMenuItem *item, gpointer user_data)
{
    menu_item_cb_common_simple(item, user_data, COMMAND_CREATE_SANDBOX);
}

static void 
menu_item_invite_friends_cb(NautilusMenuItem *item, gpointer user_data)
{
    menu_item_cb_common_simple(item, user_data, COMMAND_INVITE_FRIENDS);
}

typedef struct {
    GList               * toret;
    NautilusMenuItem    *root_item;
    NautilusMenu        *submenu;
} MenuBuilder;

static void 
menu_builder_initialize(MenuBuilder *builder) 
{
    g_assert(builder != NULL);
    builder->toret = NULL;
    builder->root_item = NULL;
    builder->submenu = NULL;

    /* 
     * There is a bug in Nautilus version <= 2.22
     *
     * taken from nautilus-file-repairer (http://repairer.kldp.net/):
     * this code is a workaround for a bug of nautilus
     * See: http://bugzilla.gnome.org/show_bug.cgi?id=508878 
     */

    /* 
     * Note: all items in return list CAN'T be released in this function,
     *       while all items in menu MUST be released in this function.
     */
    if (enable_submenu_workaround == FALSE) {
        /* first create root item */
        builder->root_item = nautilus_menu_item_new("NautilusNutstore::root_item", 
                _(MENU_LABEL_NUTSTORE),
                _(MENU_LABEL_NUTSTORE), 
                MENU_ICON_NUTSTORE); 

        builder->toret = g_list_append(builder->toret, builder->root_item);

        /* create submenu */
        builder->submenu  = nautilus_menu_new();
        nautilus_menu_item_set_submenu(builder->root_item, builder->submenu);
        g_object_unref(builder->submenu);
    }
}

static void 
menu_builder_add_item(MenuBuilder *builder, NautilusMenuItem *item, 
    GList *files, void *cb, NautilusMenuProvider *provider)
{
    g_assert(builder != NULL);
    g_assert(item != NULL);
    g_assert(files != NULL);
    g_assert(cb != NULL);
    g_assert(provider != NULL);

    if (enable_submenu_workaround) {
        builder->toret = g_list_append(builder->toret, item);
    } else {
        nautilus_menu_append_item(builder->submenu, item);
        g_object_unref(item);
    }

    g_signal_connect(item, "activate", G_CALLBACK(cb), provider);

    g_object_set_data_full(G_OBJECT(item), MENU_DATA_KEY_FILE,
            nautilus_file_info_list_copy (files),
            (GDestroyNotify)nautilus_file_info_list_free); 
}

static GList *
nautilus_nutstore_get_file_items(NautilusMenuProvider *provider,
        GtkWidget *window,
        GList *files)
{
    NautilusNutstore *nuts;

    gchar *filename;
    gboolean isdir;

    const gchar *parent_sandbox;

    MenuBuilder builder;
    NautilusMenuItem *item;

    /* Only show menu for single file or directory */
    if (g_list_length(files) != 1) {
        debug("get_file_items() list length %d != 1\n", g_list_length(files));
        return NULL;
    }

    nuts = NAUTILUS_NUTSTORE(provider);

    /* if the command client isn't connected, we can't do anything */
    if (nutstore_command_client_is_connected(&(nuts->client)) == FALSE) {
        return NULL;
    }

    filename = file_info_get_utf8(NAUTILUS_FILE_INFO(files->data));
    isdir = nautilus_file_info_is_directory(NAUTILUS_FILE_INFO(files->data));

    parent_sandbox = nutstore_find_parent_sandbox(nuts, filename);
    if (!parent_sandbox) {
        if (!isdir || nutstore_has_child_sandbox(nuts, filename)) {
            g_free(filename);
            return NULL;
        }
    }

    menu_builder_initialize(&builder);

    if (parent_sandbox == NULL) {   // outside any sync folder
        item = nautilus_menu_item_new("NautilusNutstore::create",
                _(MENU_LABEL_CREATE_SANDBOX), 
                _(MENU_TIP_CREATE_SANDBOX),
                MENU_ICON_CREATE_SANDBOX);

        // the builder take care of releasing item
        menu_builder_add_item(&builder, item, files, 
                menu_item_create_sandbox_cb, provider);

        item = nautilus_menu_item_new("NautilusNutstore::invite",
                _(MENU_LABEL_INVITE_FRIENDS), 
                _(MENU_TIP_INVIATE_FRIENDS),
                MENU_ICON_INVITE_FRIENDS);

        // the builder take care of releasing item
        menu_builder_add_item(&builder, item, files, 
                menu_item_invite_friends_cb, provider);
    } else {
        if (strcmp(filename, parent_sandbox) == 0) {    // root directory of sandbox
            item = nautilus_menu_item_new("NautilusNutstore::properties",
                    _(MENU_LABEL_SHOW_PROPERTIES), 
                    _(MENU_TIP_SHOW_PROPERTIES),
                    MENU_ICON_SHOW_PROPERTIES);

            // the builder take care of releasing item
            menu_builder_add_item(&builder, item, files, 
                    menu_item_show_properties_cb, provider);
        } else {
            // create "publish" item in submenu
            item = nautilus_menu_item_new("NautilusNutstore::publish",
                    _(MENU_LABEL_PUBLISH), 
                    _(MENU_TIP_PUBLISH),
                    MENU_ICON_PUBLISH);

            // the builder take care of releasing item
            menu_builder_add_item(&builder, item, files, 
                    menu_item_publish_cb, provider);
        } 
    }

    // remember to release memory
    g_free(filename);

    // Return the items list
    return builder.toret;
}

static void
nautilus_nutstore_menu_provider_iface_init (NautilusMenuProviderIface *iface) {
    iface->get_file_items = nautilus_nutstore_get_file_items;
    return;
}

void nautilus_nutstore_info_provider_iface_init (NautilusInfoProviderIface *iface) {
    iface->update_file_info = nautilus_nutstore_update_file_info; 
    iface->cancel_update = nautilus_nutstore_cancel_update;
    return;
}

/**
 * The nutstore instance function
 * We do all initialization here 
 */
static void
nautilus_nutstore_instance_init (NautilusNutstore *nuts) {
    /* hash tables mapping between NautilusFileInfo object and filename */
    nuts->filename2obj = g_hash_table_new_full((GHashFunc) g_str_hash,
            (GEqualFunc) g_str_equal,
            (GDestroyNotify) g_free,
            (GDestroyNotify) NULL);

    nuts->obj2filename = g_hash_table_new_full((GHashFunc) g_direct_hash,
            (GEqualFunc) g_direct_equal,
            (GDestroyNotify) NULL,
            (GDestroyNotify) g_free);

    /* initialize nutstore root directory */
    nuts->ns_root_dirs_mutex = g_mutex_new();
    nuts->ns_root_dirs = NULL;

    /* setup the connection objects */
    nutstore_command_client_setup(&(nuts->client));
    nutstore_inputcmd_server_setup(&(nuts->server));

    /* add connection hooks to reset all Nautilus file info */
    nutstore_command_client_add_on_connect_hook(&(nuts->client),
            (NutstoreCommandClientConnectHook) command_client_on_connect,
            nuts);

    nutstore_command_client_add_on_disconnect_hook(&(nuts->client),
            (NutstoreCommandClientConnectHook) command_client_on_disconnect,
            nuts);

    /* add input command handler */
    nutstore_inputcmd_server_add_handler(&(nuts->server),
            COMMAND_REFRESH_STAT,   // command name
            (NutstoreInputCommandHandler) input_command_refresh_state_handler,
            TRUE,                   // call in glib main loop
            nuts);

    nutstore_inputcmd_server_add_handler(&(nuts->server),
            COMMAND_REFRESH_NS_ROOT,    // command name
            (NutstoreInputCommandHandler) input_command_refresh_ns_root_handler,
            TRUE,                       // call in glib main loop
            nuts);

    /* now start the connection */
    debug("about to start client connection");
    nutstore_command_client_start(&(nuts->client));

    debug("about to start server connection");
    nutstore_inputcmd_server_start(&(nuts->server));

    return;
}

/**
 * Register nutstore type
 */
static GType nutstore_type;

GType nautilus_nutstore_get_type (void) {
    return nutstore_type;
}

void nautilus_nutstore_register_type (GTypeModule *module) 
{
    static const GTypeInfo info = {
        sizeof (NautilusNutstoreClass),
        (GBaseInitFunc) NULL,
        (GBaseFinalizeFunc) NULL,
        (GClassInitFunc) NULL,
        (GClassFinalizeFunc) NULL,
        NULL,
        sizeof (NautilusNutstore),
        0,
        (GInstanceInitFunc) nautilus_nutstore_instance_init,
    };

    static const GInterfaceInfo menu_provider_iface_info = {
        (GInterfaceInitFunc) nautilus_nutstore_menu_provider_iface_init,
        NULL,
        NULL
    };

    static const GInterfaceInfo info_provider_iface_info = {
        (GInterfaceInitFunc) nautilus_nutstore_info_provider_iface_init,
        NULL,
        NULL
    };

    nutstore_type = g_type_module_register_type(module,
            G_TYPE_OBJECT,
            "NautilusNutstore",
            &info, 0);

    // Add menu provider
    g_type_module_add_interface (module,
            nutstore_type,
            NAUTILUS_TYPE_MENU_PROVIDER,
            &menu_provider_iface_info);

    // add info provider
    g_type_module_add_interface (module,
            nutstore_type,
            NAUTILUS_TYPE_INFO_PROVIDER,
            &info_provider_iface_info);
}

